package com.my.study.jdbc.aop;

import com.my.study.jdbc.entity.TransactionParam;
import com.my.study.utils.SpringContextUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author Carlos
 * @version 1.0
 * @Description TODO
 * @date 2021/11/7 13:07
 **/
@Aspect
@Component
public class MultiTransactionalAspect {

    @Pointcut("@annotation(com.my.study.jdbc.aop.MultiTransactional)")
    public void point() {}

    @Around(value = "point()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object obj = null;
        boolean doIt = false;

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();

        List<TransactionParam> tsParams = null;

        MultiTransactional multiTransactional = method.getAnnotation(MultiTransactional.class);
        if (null != multiTransactional) {
            // 隔离级别
            Isolation isolation = multiTransactional.isolation();
            // 传播行为
            Propagation propagation = multiTransactional.propagation();

            String[] groups = multiTransactional.value();
            if (groups.length > 0) {
                doIt = true;

                Set<String> noRollbackForNameSet = new HashSet<>();
                Class<? extends Throwable>[] noRollbackFor = multiTransactional.noRollbackFor();
                if(noRollbackFor.length > 0 ){
                    for ( Class<? extends Throwable> clazz : noRollbackFor ) {
                        noRollbackForNameSet.add(clazz.getSimpleName().toLowerCase());
                    }
                }

                tsParams = new ArrayList<>(groups.length);

                try {
                    for (String tx : groups) {
                        TransactionParam tsParam = new TransactionParam();
                        // 事务管理器
                        tsParam.setTm(SpringContextUtil.getBean(tx, DataSourceTransactionManager.class));
                        // 事务定义
                        tsParam.setDef(new DefaultTransactionDefinition());
                        tsParam.getDef().setIsolationLevel(isolation.value());
                        tsParam.getDef().setPropagationBehavior(propagation.value());
                        // 事务状态
                        tsParam.setStatus(tsParam.getTm().getTransaction(tsParam.getDef()));

                        tsParams.add(tsParam);
                    }

                    obj = pjp.proceed();

                    // 提交事务
                    for (TransactionParam tsParam : tsParams) {
                        tsParam.getTm().commit(tsParam.getStatus());
                    }

                } catch (Throwable e) {
                    //事务回滚
                    boolean rollback = true;
                    String errName = e.getClass().getName().toLowerCase();

                    if( !"transactionexception".equals(errName) && noRollbackForNameSet.contains(errName)){
                        rollback = false;
                    }

                    // 事务不回滚
                    if (!rollback) {
                        try {
                            for(TransactionParam tsParam : tsParams){
                                tsParam.getTm().commit(tsParam.getStatus());
                            }
                            //但是还是要抛出异常
                            throw e;
                            // end
                        } catch (org.springframework.transaction.TransactionException e1) {
                            e1.printStackTrace();
                        }
                    }


                    //事务回滚
                    for(TransactionParam tsParam : tsParams){
                        tsParam.getTm().rollback(tsParam.getStatus());
                    }
                    throw e;
                }
            }
        }

        if (!doIt) {
            obj = pjp.proceed();
        }

        return obj;
    }

}
